#include "digr.h"

///////////////////////////////////////////////////////////

DiGrNode::DiGrNode() {

}

void DiGrNode::addInEdge(DiGrEdge *e) {
  _inEdges.push_back(e);
}

void DiGrNode::addOutEdge(DiGrEdge *e) {
  _outEdges.push_back(e);
}

int DiGrNode::InEdges() {
  return _inEdges.size();
}

int DiGrNode::OutEdges() {
  return _outEdges.size();
}

DiGrEdge* DiGrNode::getInEdge(int index) {
  if (index < 0 || index >= _inEdges.size()) {throw "DiGr run-time error: attempting to index an incoming edge which doesn't exist!"; }
  return _inEdges[index];
}

DiGrEdge* DiGrNode::getOutEdge(int index) {
  if (index < 0 || index >= _outEdges.size()) {throw "DiGr run-time error: attempting to index an outgoing edge which doesn't exist!"; }
  return _outEdges[index];
}

DiGrNode* DiGrNode::getParent(int index) {
  if (index < 0 || index >= _inEdges.size()) {throw "DiGr run-time error: attempting to index a parent node which doesn't exist!"; }
  return (getInEdge(index))->outNode();
}

DiGrNode* DiGrNode::getChild(int index) {
  if (index < 0 || index >= _outEdges.size()) {throw "DiGr run-time error: attempting to index a child node which doesn't exist!"; }
  return (getOutEdge(index))->inNode();
}

void DiGrNode::setAttribute(string attrName, AttributeType attrValue) {
  
  if (_attributes.count(attrName) == 0) {
    // create this attribute for the first time
    _attributes.insert(std::pair<std::string,AttributeType>(attrName,attrValue));
    
  } else {
    // find the attribute and modify it
    _attributes[attrName] = attrValue;
  }
}

AttributeType DiGrNode::getAttribute(string attrName) {

  if (_attributes.count(attrName) == 0) {
    // create this attribute for the first time
    _attributes.insert(std::pair<std::string,AttributeType>(attrName,(AttributeType) 0));
    return 0;
    
  } else {
    // find the attribute and return it
    return _attributes[attrName];
  }

}

///////////////////////////////////////////////////////////

DiGrEdge::DiGrEdge(DiGrNode *fromNode, DiGrNode *toNode, bool Uedge) {
  (*fromNode).addOutEdge(this);
  (*toNode).addInEdge(this);
  _inNode = toNode;
  _outNode = fromNode;

  if (Uedge) {
    new DiGrEdge(toNode, fromNode, false);
  }

}

DiGrEdge::DiGrEdge() {
  _inNode = new DiGrNode();
  _outNode = new DiGrNode();
}

DiGrNode* DiGrEdge::inNode() {
  return _inNode;
}

DiGrNode* DiGrEdge::outNode() {
  return _outNode;
}

void DiGrEdge::setAttribute(string attrName, AttributeType attrValue) {
  
  if (_attributes.count(attrName) == 0) {
    // create this attribute for the first time
    _attributes.insert(std::pair<std::string,AttributeType>(attrName,attrValue));
    
  } else {
    // find the attribute and modify it
    _attributes[attrName] = attrValue;
  }
}

AttributeType DiGrEdge::getAttribute(string attrName) {

  if (_attributes.count(attrName) == 0) {
    // create this attribute for the first time
    _attributes.insert(std::pair<std::string,AttributeType>(attrName,(AttributeType) 0));
    return 0;
    
  } else {
    // find the attribute and return it
    return _attributes[attrName];
  }

}

string globalProperty;

bool edgeSorterDescending (DiGrEdge *e1, DiGrEdge *e2) { 
  return e1->getAttribute(globalProperty) < e2->getAttribute(globalProperty); 
}
bool edgeSorterAscending (DiGrEdge *e1, DiGrEdge *e2) { 
  return e1->getAttribute(globalProperty) > e2->getAttribute(globalProperty); 
}
bool nodeSorterDescending (DiGrNode *n1, DiGrNode *n2) { 
  return n1->getAttribute(globalProperty) < n2->getAttribute(globalProperty); 
}
bool nodeSorterAscending (DiGrNode *n1, DiGrNode *n2) { 
  return n1->getAttribute(globalProperty) > n2->getAttribute(globalProperty); 
}


void DiGrAddBy(DiGrNode *current, deque<DiGrNode*> *queue, AddByWhere addWhere, AddByObject addObj, string property, AddByOrder order, int max) {
  

  // set the global property & check how many to return
  globalProperty = property;
  if (max > current->OutEdges()) max = current->OutEdges();
  if (max == 0) max = current->OutEdges();

  // push back edges and nodes
  vector<DiGrEdge*> allEdges;
  for (int e = 0; e < current->OutEdges(); e++) {
    allEdges.push_back(current->getOutEdge(e));
  }

  vector<DiGrNode*> allNodes;
  for (int n = 0; n < current->OutEdges(); n++) {
    allNodes.push_back(current->getChild(n));
  }

  // sort pointers as appropriate
  if (addObj == ADDBY_NODE) {
    if (order == DESCENDING) sort(allNodes.begin(), allNodes.end(), nodeSorterDescending);
    else sort(allNodes.begin(), allNodes.end(), nodeSorterAscending);
    for (int n = current->OutEdges() - max; n < current->OutEdges(); n++) {
      if (addWhere == FRONT) queue->push_front(allNodes[n]);
      if (addWhere == BACK) queue->push_back(allNodes[n]);
    }
  }

  if (addObj == ADDBY_EDGE) {
    if (order == DESCENDING) sort(allEdges.begin(), allEdges.end(), edgeSorterDescending);
    else sort(allEdges.begin(), allEdges.end(), edgeSorterAscending);
    for (int n = current->OutEdges() - max; n < current->OutEdges(); n++) {
      if (addWhere == FRONT) queue->push_front(allEdges[n]->inNode());
      if (addWhere == BACK) queue->push_back(allEdges[n]->inNode());
    }
  }

  // TODO: implement reverse sort with reverse iterator


}
